/************************************************
info: 这里作为tcp的服务端，其实也有数据的粘包，半包问题，期望对其进行处理
data: 2022/02/10
author: hlp
************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//tcp是可靠的流式传输 我们能保证它可靠，顺序的接收到，这里是放在tcp的接收缓冲区中
//但是放入缓冲区中，我们取数据的方案，需要做控制，以识别特定的不同的包。（有的包很小，有的包很大，取数据时要注意）
//汇总：我们从缓存中取数据，要关注数据的完整，一次是否能取到完整的包。
int exec_one_data(char* data, int len);
void check_buff(char * ringbuff, int *ops, int buff_size);//第三个参数为了在处理成功后清空用

void recv_data_by_specific_tail_symbol(int fd);
void recv_data_by_length_and_data(int fd);
int main()
{
	//要解决识别数据的完整性问题 我们需要适配用户层协议  特定字节/特定终止符/长度+data

	int fd = 0;
	//特定的终止符+缓冲区处理   这里我是项目被要求使用这种复杂的头和尾标识，就这样演示 其实只要尾部也可以保证的
	recv_data_by_specific_tail_symbol(fd);
	//按照长度+data的方案也是一种可靠方案，   先接收特定字节的长度，再接收数据。
	recv_data_by_length_and_data(fd);
	return 0;
}

//发送时 特定的尾部标识+缓冲区方案
//每次不知道取多少数据，以及是否取到完整数据，一定需要缓冲区
//作为服务端  我们是有多个客户端fd连接的   最佳方案其实是每个fd连接应该有自己的缓冲区
//    假设我们的数据都能一次发送(业务不会有拆包现象)，那么直接一个缓冲区做粘包的解析处理即可 （有拆包的话就会有问题的）
void recv_data_by_specific_tail_symbol(int fd)
{
	//缓冲区可以使用ringbuffer 这里demo只是演示，用了一块内存，并且所有连接公用一个（假设没有拆包，只是验证思路）
//client 发送 假设构造发送数据  依次再客户端进行发送了 业务不涉及多包
	const char * send1_data = "FFFF0D0A<header>my test of send 1. \\<tail>0D0AFEFE";
	const char * send2_data = "FFFF0D0A<header>my test of send 2. \\<tail>0D0AFEFE";
	const char * send3_data = "FFFF0D0A<header>my test of send 3. \\<tail>0D0AFEFE";

	//tcp是可靠的 流式传输，必然按顺序，完整的收到一个包，接收放入缓冲区后，依次处理就好
//server 接收 由于我recv时不知道接收的长度，可能每次接收特定len（可能小于一个单包，可能刚好截断缓冲区某个包）
	//所以  放在缓冲区中，判断缓冲区中“FFFF0D0A<header><tail>0D0AFEFE”头和尾的标识进行处理，我是每次接收后判断一次，可以定时器等其他方案
	//len = recv(fd, data, 44, 0);  memcpy(ringbuff +ops, data, len); check_buff(ringbuff, ops);
	//每个fd使用一个缓冲区是最佳方案，这里用一块内存进行简单测试处理
	char * ringbuff = (char *) malloc(1024); //假设缓冲区大小定义为1024
	memset(ringbuff, 1024, 0);
	int ops = 0;  
	//假设接收到数据 先放入缓冲区中 这里假设客户端发送的数据都符号标准，当然要做安全防护
	//假设我取数据两次取到 "FFFF0D0A<header>my test of send 1. \\<tail>0D0AFEFEFFFF0D0A<header>my test"
	//					 " of send 2. \\<tail>0D0AFEFEFFFF0D0A<header>my test of send 3. \\<tail>0D0AFEFE"
	//len = recv(fd, recv1_data, my_len,0); my_len是我提前定义的recv1_data大小，这种情况应该是my_len == len
	//第一次recv提取
	const char* recv1_data = "FFFF0D0A<header>my test of send 1. \\<tail>0D0AFEFEFFFF0D0A<header>my test";
	memcpy(ringbuff+ops, recv1_data, strlen(recv1_data));
	ops += strlen(recv1_data);
	//消费缓冲区位置   修改ops
	check_buff(ringbuff, &ops, 1024); //校验缓冲区中是否有完整数据  有则处理 识别第一个FFFF0D0A<header> 到下一个<tail>0D0AFEFE
	//第二次recv提取 
	const char* recv2_data =" of send 2. \\<tail>0D0AFEFEFFFF0D0A<header>my test of send 3. \\<tail>0D0AFEFE";
	memcpy(ringbuff+ops, recv2_data, strlen(recv2_data));
	ops += strlen(recv2_data);
	check_buff(ringbuff, &ops, 1024); //这里是正常数据 应该已经全部处理了
	printf("ringbuff length is [%d] \n", ops);

	memset(ringbuff, 1024, 0); //每次处理完要清空 很有必要  不然下次处理也会有问题
	if(ringbuff)
	{
		free(ringbuff);
		ringbuff = NULL;
	}

}

//这个函数其实是recv后，放入ringbuff后的主要解析逻辑
//这里的处理与recv的逻辑也有关   尽量一次recv循环取完，则每次数据都是能完整处理 （否则其实会有半包的现象）
void check_buff(char * ringbuff, int *buffops, int buff_size)
{
	//循环一次取完 应该就不会有这种问题 但是半包问题肯定有
	if(*buffops <= strlen("FFFF0D0A<header><tail>0D0AFEFE"))
	{
		return;
	}

	printf ("check buff tail is [%s] \n", ringbuff+(*buffops)-strlen("<tail>0D0AFEFE"));

	//对比终结符相同再处理 否则留给下一次
	if(strcmp("<tail>0D0AFEFE", ringbuff+(*buffops)-strlen("<tail>0D0AFEFE")) !=0)
	{
		return;
	}

	//对接收到的数据做拆包处理
	int datalen = -1;
	char * onedata;
	char * ops;
	char * temp_data = ringbuff;
	//先判断是否有结尾的包 再判断头进行处理
	const char * end_str = "<tail>0D0AFEFE";
	//每次取一个尾部  然后处理一个包
	while((ops = strstr(temp_data, end_str)) != NULL)
	{
		datalen = ops - temp_data +strlen(end_str);
		exec_one_data(temp_data, datalen);
		temp_data = ops+strlen(end_str);
	}

	//有剩下的数据 这是不可能的  因为recv是循环取完放在缓冲区中的
	if(temp_data - ringbuff != *buffops)
	{
		printf("there is loss data: [%ld][%s] \n", strlen(temp_data), temp_data);
	}
	//清空处理
	memset(ringbuff, 1024, 0);
	buffops = 0;
}

//这是一个完整的发送数据包   FFFF0D0A<header> XXX <tail>0D0AFEFE
int exec_one_data(char* data, int len)
{
	const char * start_str = "FFFF0D0A<header>";
	char * ops;
	ops = strstr(data, start_str);
	if(ops == data) //如果中间包含header，解析有误，但是应该是不可能的
	{
		int out_len = len-strlen("FFFF0D0A<header><tail>0D0AFEFE");
		char * out_data = NULL;
		out_data =(char*)malloc(out_len +1);
		memset(out_data, out_len+1, 0);
		memcpy(out_data, data + strlen("FFFF0D0A<header>"), out_len);
		printf("out_data is [%lu][%s] \n", strlen(out_data), out_data);
		if(out_data != NULL)
		{
			free(out_data);
			out_data = NULL;
		}
	}	

	if(ops == NULL) //没有找到头  丢弃
	{
		printf("package data is error, not find start data. \n");
	}

	if(ops != data) //头前面有异常数据
	{
		printf("recv package data is error.");
	}

	return 0;
}
//发送时 特定的字节存储数据长度+实际data   
//个人理解  这种按照特定的结构取数据 不需要缓冲区是可以保证的
void recv_data_by_length_and_data(int fd)
{
//构造发送的数据
	const char * send_data_str = "my test of send data \\";
	unsigned short len = strlen(send_data_str);
	printf("vps short is : %lu \n", sizeof(unsigned short)); //vps short is : 2 
	//用2个字节的特定长度 +data的结构进行数据的构造
	//这里要转网络序 取出后再转回来
	char * send_data = NULL;
	send_data = (char*)malloc(2 +len +1);//预留了一个终结符
	memset(send_data,  2+len, 0);
	memcpy(send_data, &len, 2);
	memcpy(send_data+2, send_data_str, len);
	//这里应该按照十六进制打印特定的长度  last send data is [22][my test of send data \]  
	printf("last send data is [%u][%s]  \n", *((unsigned short *)send_data), send_data+2);

//接收端处理 应该先接收两字节长度 再接收后面数据长度
	//这里发送的其实就是send_data  接收端先接收两个字节的长度，再接收特定长度的数据 (这里直接做解析)
	unsigned short recv_data_len;
	memcpy(&recv_data_len, send_data, 2);
	printf("recv data is[%d: %s] \n", recv_data_len, send_data+2); //recv data is[22: my test of send data \] 

//注意发送端数据的free
	if(send_data != NULL)
	{
		free(send_data);
		send_data = NULL;
	}
}



